package roborallyimplementation;

import be.kuleuven.cs.som.annotate.*;

/**
 * A class of elements that can be placed on a RoboRally board.
 * 
 * @invar	If this element is located on an effective board, it cannot be placed on any other board.
 * 			|	if (this.getBoard != null) then (if otherboard != this.getBoard() then ! canHaveAsBoard(otherboard))
 * @invar	This element can have its board as board.
 * 			|	this.canHaveAsBoard(this.getBoard())
 * @author Thomas Vochten (1ba inform.), Daan Daele (1ba inform.)
 *
 */

public abstract class Element {
	
	/**
	 * Returns the board this element is located on.
	 */
	@Basic
	public Board getBoard()
	{
		return board;
	}
	
	/**
	 * Places this element on the given board.
	 * 
	 * @param	board
	 * 			The board this element is to be placed on.
	 * @throws	IllegalArgumentException
	 * 			This element cannot have the given board as board.
	 * 			|	! this.canHaveAsBoard(board)
	 */
	@Raw
	public void setBoard(Board board) throws IllegalArgumentException
	{
		if (this.isTerminated())
			return;
		if (! canHaveAsBoard(board))
			throw new IllegalArgumentException();
		if (this.getBoard() != null)
			this.getBoard().removeElement(this);
		this.board = board;
	}
	
	/**
	 * Determines whether this element can have the given board as board.
	 * 
	 * @param	board
	 * 			The board to be considered.
	 * @return	True if and only if the given board is equal to this element's board, or if this
	 * 			element has a non-effective board and the given board is effective,
	 * 			or if this element has an effective position and that position is a valid position
	 * 			on the given board, or if none of the previous conditions have been met and the
	 * 			given board is non-effective.
	 * 			|	if (board == this.getBoard()) then result == true
	 * 			|	if (this.getBoard() == null) then result == (board != null)
	 * 			|	if (this.getPosition() != null) then result == board.isValidPosition(this.getPosition())
	 * 			|	else result == (board == null)
	 */
	@Raw
	public boolean canHaveAsBoard(Board board)
	{
		if (this.isTerminated()) return false;
		if (board == this.getBoard())
			return true;
		if (this.getBoard() == null)
			return board != null;
		if (this.getPosition() != null && board != null)
			return board.isValidPosition(this.getPosition());
		return board == null;
	}
	
	private Board board;
	
	/**
	 * Returns the position this element is placed on.
	 */
	@Basic
	public Position getPosition()
	{
		return position;
	}
	
	/**
	 * Determines whether this element can be placed on the given position.
	 * 
	 * @param	position
	 * 			The position to be considered.
	 * @return	True if and only if the given position is equal to this element's position, or
	 * 			if this element has an effective board as board and the given position is a valid position
	 * 			on that board, or if previous conditions have not been met and the position is either
	 * 			non-effective or the position's coordinates fall within acceptable bounds as defined by Board.
	 * 			|	if (position == this.getPosition()) then result == true
	 * 			|	if (this.getBoard() != null) then result == this.getBoard().isValidPosition(position)
	 * 			|	else result == (position == null || (position.getX() >= 0 && position.getX() <= Board.getMaxWidth()
							&& position.getY() >= 0 && position.getY() <= Board.getMaxHeight()))
	 */
	@Raw
	public boolean canHaveAsPosition(Position position)
	{
		if (this.isTerminated()) return false;
		if (position == this.getPosition())
			return true;
		if (this.getBoard() != null)
			return this.getBoard().isValidPosition(position);
		return (position == null || (position.getX() >= 0 && position.getX() <= Board.getMaxWidth()
				&& position.getY() >= 0 && position.getY() <= Board.getMaxHeight()));
	}
	
	/**
	 * Determines whether this element can share its position with the given element.
	 * 
	 * @param	other
	 * 			The element this element will be compared with.
	 * @return	False if the other element is non-effective
	 * 			or if this element has been terminated, true
	 * 			if the other element is actually the same
	 * 			element as this element; behaviour in other cases is undefined at this level.
	 * 			|	if (other == null) then result == false
	 */
	public boolean canSharePositionWith(Element other)
	{
		if (this.isTerminated())
			return false;
		if (other == null)
			return false;
		if (other == this)
			return true;
		return true;
	}
	
	/**
	 * Sets the position this element is placed on to the given position.
	 * 
	 * @param	position
	 * 			The position this element will be placed on.
	 * @post	This element will have been placed on the given position.
	 * 			|	new.getPosition() == position
	 * @throws	IllegalPositionException
	 * 			This element cannot have the given position as position.
	 * 			|	! canHaveAsPosition(position)
	 */
	@Raw
	public void setPosition(Position position) throws IllegalPositionException
	{
		if (this.isTerminated())
			return;
		if (! canHaveAsPosition(position))
			throw new IllegalPositionException("Attempted to add an object to an invalid position", 
					position.getX(), position.getY());
		if (this.getPosition() != null)
			this.getPosition().getElements().remove(this);
		this.position = position;
	}
	
	private Position position;
	
	/**
	 * Determines whether this element equals the given object.
	 * 
	 * @param	object
	 * 			The object to be compared.
	 * @return	False if the object is non-effective or if it cannot be cast to Element.
	 * 			|	if (object == null)
	 * 			|		then result == false
	 * 			|	if (! Element.class.isInstance(object)
	 * 			|		then result == false
	 */
	@Override
	public abstract boolean equals(Object object);
	
	/**
	 * Calculates the hash code corresponding to this element.
	 * 
	 * @return	An integer.
	 */
	@Override
	public abstract int hashCode();
	
	/**
	 * Returns a string representation of this element.
	 * 
	 * @return	The string representation.
	 */
	@Override
	public abstract String toString();
	
	/**
	 * Terminates this element.
	 * 
	 * @post	If this element has an effective board, the reference to this board will be nullified
	 * 			and this element removed from that board.
	 * 			|	if (this.getBoard() != null) then new.getBoard() == null &&
	 * 			! this.getBoard().getElements(this.getPosition()).contains((new this))
	 * @post	If this element is placed on an effective position, the reference to this
	 * 			position will be nullified as will the reference from that position to this element.
	 * 			|	if (this.getPosition() != null) then new.getPosition() == null &&
	 * 			! this.getPosition().getElements().contains((new this))
	 */
	public void terminate()
	{
		if (this.isTerminated())
			return;
		if (getBoard() != null)
		{
			getBoard().removeElement(this);
			this.setBoard(null);
			if (getPosition() != null)
			{
				getPosition().removeElement(this);
				this.setPosition(null);
			}
			isTerminated = true;
		}
		else 
		{
			if (getPosition() != null)
			{
				getPosition().removeElement(this);
				this.setPosition(null);
			}
			isTerminated = true;
		}
	}
	
	/**
	 * Returns whether this element has been terminated.
	 */
	@Basic
	public boolean isTerminated()
	{
		return isTerminated;
	}
	
	private boolean isTerminated = false;
}
